SSH 密钥管理
Table of Contents
1. 密钥是什么
密钥 key 是一个或一对非常大的数字,通过加密算法得到。对称加密只需要一个密钥。非对称加密需要两个密钥成对使用,分为公钥 public key 和私钥 private key 。
公钥和私钥一一对应,每一个私钥都有且仅有一个对应的公钥,反之亦然。私钥自己持有,不能泄露;公钥可以对外发送。
如果数据被公钥加密,那么有且只有对应的私钥才能解密;如果数据被私钥加密 (这个过程一般称为 签名),也只有使用对应的公钥解密。
SSH 身份认证可以采用非对称加密密钥认证,每个用户凭各自生成的密钥对登录。
2. 用 ssh-keygen 命令生成密钥对
2.1. 基本用法
OpenSSH 提供了指令程序 ssh-keygen
用来生成密钥。
需要提一下,在非对称加密中,公钥和私钥不可互推,所以,知道公钥不能推出私钥,反之亦然。
有读者问为什么用 openssl genrsa -out rsak 3072
生成一个私钥以后,可以用 openssl rsa -in rsak -pubout -out rsap
把公钥提取出来,然而理论上又不能从私钥得到公钥。实际上,rsak 文件并不是私钥文件,rsak 文件包含私钥和公钥的描述信息两部分。用 openssl rsa -text -in rsak
可以看到 rsak 文件中包含了什么东西,其中 modulus, publicExponent 两个字段描述了公钥的信息,提取公钥的步骤只是根据这些信息把公钥写到另一个地方而已。
而 ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
能提取公钥的原因也是 ~/.ssh/id_rsa 私钥文件中包含了公钥的描述信息,提取公钥只需要根据描述信息把公钥写到另一个地方即可。
ssh-keygen -t <encryption-method>
中 -t
参数指定了生成密钥对的算法。可选 dsa
, rsa
, ecdsa
, ecdsa-sk
, ed25519
, ed25519-sk
等。
其中:
- dsa 和 rsa 算法出现较早,对旧设备的兼容性好:
- dsa 算法生成的密钥长度固定 (早期 1024 位,后期 2048 位),验证速度快但安全性很差,依赖随机数生成器,已不再使用
- rsa 算法生成的密钥验证速度慢但比 dsa 更安全,且不依赖随机数生成器,所以现在依旧在使用
- dsa 算法的安全性依赖于高质量的随机数生成器
- rsa 算法当前默认使用的密钥长度为 3072 位, 但增加 RSA 密钥的长度并不会等比例地提高安全性
- ecdsa 算法是 dsa 算法结合 ECC (Elliptic Curve Cryptograph) 椭圆曲线算法的实现:
- openssh 中的 ecdsa 算法是 dsa 算法结合 NIST 椭圆曲线算法的实现
- 它的安全性依赖于高质量的随机数生成器 (索尼 PS3 被破解的原因是它的 ecdsa 密钥使用了有缺陷的随机数生成算法)
- 可以通过 -b 参数显式指定 NIST 椭圆曲线的大小,来隐式指定密钥长度,无法直接指定密钥长度
- NIST 椭圆曲线的大小 (
-b
参数) 只能选 256, 384, 521 其中之一,分别对应 NIST P-256, NIST P-384, NIST P-521 三个曲线标准
- ed25519 算法是 dsa 算法结合 ECC (Elliptic Curve Cryptograph) 椭圆曲线线算法的实现:
- openssh 中的 ed25519 算法是 dsa 算法结合 Ed25519 椭圆曲线算法的实现
- 在概念上:
- ed25519 算法是 eddsa 算法结合 Ed25519 椭圆曲线算法的实现
- eddsa 算法是 dsa 算法结合 Twisted Edwards Curve (一种椭圆曲线的平面模型) 椭圆曲线算法的实现
- 可以认为 ed25519 算法是 eddsa 算法的一种特例
- eddsa 算法可以避免由随机数生成器的缺陷导致的安全问题
- 以上提到的内容都没有专利问题
- 它可以避免由随机数生成器的缺陷导致的安全问题
- 自 OpenSSH 6.5 引入,安全性好于 dsa, ecdsa, eddsa
- ed25519 算法生成的密钥长度固定,所以
-b
参数不起作用
- 以 -sk (with Secure Key) 结尾的密钥类型必须与 FIDO U2F 硬件令牌一起使用,一般不用这种密钥
NIST P-256, NIST P-384, NIST P-521 这三种曲线标准是美国国家标准技术研究所推出的标准。业界怀疑 NSA 在随机数生成器中留有可以用来推算私钥的后门,所以欧洲推出了 Brainpool P-256, Brainpool P-384, Brainpool P-521 曲线标准。比特币使用 secp256k1 曲线,由 SECG (Standards for Efficient Cryptography Group) 推出,不用担心后门问题。
所以,建议,旧设备使用 rsa 算法生成长度为 3072/4096 位的密钥,新设备使用 ed25519 算法生成密钥。
比如要生成一个使用 ed25519 算法的密钥,执行 ssh-keygen -t ed25519
后,会要求用户回答一些问题:
Generating public/private ed25519 key pair. Enter file in which to save the key (/home/<username>/.ssh/id_ed25519): # 密钥文件的存储路径,按 Enter 保持默认 Enter passphrase (empty for no passphrase): # 使用密钥文件时需要输入的密码 Enter same passphrase again: # 使用密钥文件时需要输入的密码 Your identification has been saved in /home/<username>/.ssh/id_ed25519 Your public key has been saved in /home/<username>/.ssh/id_ed25519.pub The key fingerprint is: SHA256:IDJNSlpwsT4RiyEIiuduC98yJTAXVSFd+LFt2oU5U64 <username>@<hostname> The key's randomart image is: +--[ED25519 256]--+ |B.*o+oo+. | |=B.B .o . . | |= O.o .. + = | |o+.+ . .o B o | | ++ S+ = | | .... . E | |. oo | | +oo | | oo. | +----[SHA256]-----+
示例中,第一个问题询问密钥保存路径,默认是 ~/.ssh/id_ed25519 文件,这个是私钥的文件名,对应的公钥文件 ~/.ssh/id_ed25519.pub 是自动生成的。
如果选择 rsa 算法,密钥文件默认是 ~/.ssh/id_rsa (私钥) 和 ~/.ssh/id_rsa.pub (公钥)。
第二个问题,询问是否给私钥文件设密码 (passphrase)。这样即使入侵者拿到私钥,还需要破解密码才能使用。如果为了方便不设密码,直接按回车键密码就会为空。接着是第二次密码确认,两次输入必须一致。注意,这里 密码 的英文单词是 passphrase ,避免与 Linux 账户的密码单词 password 混淆。
最后生成私钥和公钥,屏幕上给出公钥的指纹以及当前的用户名和主机名作为注释,用于识别密钥的来源。
公钥文件和私钥文件都是文本文件,自动生成的公钥文件通常以 .pub
结尾。公钥文件的内容类似下面这样。
$ cat ~/.ssh/id_ed25519.pub ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqgAHzoVuCQncg7w5sWuSxmIs7Vg3dCVnuwWWdvUPYB <username>@<hostname>
示例中,末尾的 <username>@<hostname>
是公钥的注释,用来识别不同的公钥,表示这是主机 <hostname>
上的用户 <username>
的公钥。注释不是必需项。
给密钥文件正确的权限: chmod 600 ~/.ssh/id_ed25599 && chmod 600 ~/.ssh/id_ed25599.pub
否则 ssh 将不使用这些密钥文件。
2.2. 配置项
ssh-keygen 命令的常用配置项有下面这些:
-b
指定密钥的二进制长度。密钥越长越不容易被破解,对应地,算力开销也会增加,但增加密钥长度并不会等比例提高安全性-C
为密钥文件指定新的注释-f
指定生成的私钥文件:ssh-keygen -t ed25519 -f <key-file>
指定私钥文件<key-file>
和公钥文件<key-file>.pub
的路径-F
检查某个主机名是否在 known_hosts 文件里面:ssh-keygen -F <host>
-N
指定私钥的密码 passphrase:ssh-keygen -t ed25519 -N <password>
-p
重新指定私钥密码。与-N
的不同之处在于,新密码不在命令中指定,而是执行后再输入。ssh 先要求输入旧密码,然后要求输入两遍新密码-R
将指定的主机公钥指纹移出 ~/.ssh/known_hosts 文件:ssh-keygen -R example.com
-t
指定生成密钥的加密算法
比如 ssh-keygen -t rsa -b 4096 -C "<comment>"
命令生成一个 4096 位 RSA 加密算法的密钥对,并加注释。(OpenSSH 当前默认的 rsa 密钥长度是 3072)
3. 手动上传公钥
生成密钥以后,公钥必须上传到服务器,才能正常使用密钥对登录。
OpenSSH 规定,用户公钥保存在服务器的 ~/.ssh/authorized_keys 文件。要登陆的用户的密钥必须保存在该用户主目录 ~/.ssh/authorized_keys 文件中。只要把公钥添加到这个文件之中,就相当于公钥上传到服务器了。每个公钥占据一行。如果该文件不存在,可以手动创建。
用户可以手动编辑该文件,把公钥粘贴进去,也可以在本机计算机上,执行下面的命令:
cat ~/.ssh/id_rsa.pub | ssh <user_name>@<host> "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
<user_name>@<host>
替换成所要登录的用户名和主机名。
ssh 为了安全,对属主的目录和文件权限有所要求。如果权限不对,则 ssh 密钥或公钥不生效:
- 用户家目录权限为 755 或者 700,不能是 77x 或 777,即保证 other 用户不能有 w 权限。
- ~/.ssh 目录权限一般为 755 或者 700。
- 公钥文件 (
rsa_id.pub
及 ~/.ssh/authorized_keys) 权限一般为 644 或 600。 - 私钥权限必须为 600。
只要公钥上传到服务器,下次登录时,OpenSSH 就会自动采用密钥登录,不再提示输入密码。
4. 用 ssh-copy-id 命令自动上传公钥
OpenSSH 自带一个 ssh-copy-id
命令,可以自动将公钥记录到远程服务器的 ~/.ssh/authorized_keys 文件中。如果文件不存在则自动创建该文件。
在本地计算机执行 ssh-copy-id -i <key-file> <user_name>@<host>
将本地的公钥拷贝到服务器。其中 -i
参数指定公钥文件, user_name
是所要登录的账户名, <host>
是服务器地址。如果省略用户名,默认为当前的本机用户名。
注意,公钥文件可以不指定路径和 .pub 后缀名,ssh-copy-id 会自动在 ~/.ssh 目录里面寻找。比如执行 ssh-copy-id -i id_rsa <user_name>@<host>
公钥文件会自动匹配到 ~/.ssh/id_rsa.pub 。
ssh-copy-id 采用密码登录,系统会提示输入远程服务器的密码。
注意: ssh-copy-id 直接将公钥添加到 ~/.ssh/authorized_keys 文件的末尾。如果文件的末尾不是一个换行符,会导致新的公钥添加到前一个公钥的末尾,两个公钥连在一起,使得两条公钥都无法生效。
所以,如果 ~/.ssh/authorized_keys 文件已经存在,使用 ssh-copy-id 命令之前,务必保证文件的末尾是换行符 (即,文件末尾有一个空行)。
5. ssh-agent 与 ssh-add 命令
5.1. 基本用法
私钥设置了密码以后,每次使用都必须输入密码,有时让人感觉非常麻烦。比如,连续使用 scp 命令远程拷贝文件时,每次都要求输入密码。
ssh-agent
命令就是为了解决这个问题而设计的,它让用户在整个 Bash 对话 (session) 之中,只在第一次使用 SSH 命令时输入密码,然后将私钥保存在内存中,后面都不需要再输入私钥的密码了。
第一步,推荐的方式是用 ssh-agent $SHELL
新建一个子 SHELL,ssh-agent 进程会随子 SHELL 退出结束。
如果想在当前对话启用 ssh-agent,可以使用 :
# linux 命令,注意是反引号 $ eval `ssh-agent` # windows 命令 $ eval $(ssh-agent)
ssh-agent 会先自动在后台运行,并将需要设置的环境变量输出在屏幕上,类似下面这样。
SSH_AUTH_SOCK=/tmp/ssh-AUBdzk7V7WAR/agent.5861; export SSH_AUTH_SOCK; SSH_AGENT_PID=5862; export SSH_AGENT_PID; echo Agent pid 5862;
eval
的作用,就是运行上面的,执行 ssh-agent
命令后的输出,设置环境变量。
注意,不建议以这种方式在当前对话启用 ssh-agent。 使用 eval 启动的 ssh-agent 进程必须被手动杀死,不会随 SHELL 退出。
第二步,在新建的 Shell 对话里面,使用 ssh-add </path/to/private-key-file>
命令添加默认的私钥文件。
$ ssh-add ~/.ssh/id_rsa Enter passphrase for /home/you/.ssh/id_dsa: ******** Identity added: /home/you/.ssh/id_dsa (/home/you/.ssh/id_dsa)
上面例子中,添加私钥时,会要求输入密码。以后,在这个对话里面再使用密钥时,就不需要输入私钥的密码了,因为私钥已经加载到内存里面了。
第三步 ssh <user_name>@<remote_host>
正常登录远程服务器。
ssh 使用的是默认的私钥。这时如果私钥有密码,ssh 不再询问密码,直接取出内存里面的私钥。
如果要使用其他私钥登录服务器,需要指定私钥文件 ssh –i ~/.ssh/<key_file> <remote_host>
。
如果要退出 ssh-agent,可以 Ctrl+d
直接退出子 Shell,也可以执行 ssh-agent -k
退出。
5.2. ssh-add 命令
ssh-add
命令用来将私钥加入 ssh-agent,它有如下的参数。
-d
从内存中删除指定的私钥:ssh-add -d name-of-key-file
-D
从内存中删除所有已经添加的私钥:ssh-add -D
-l
列出所有已经添加的私钥:ssh-add -l
5.3. 多主机配置实例
# gitlab Host git.iboxpay.com HostName git.iboxpay.com PreferredAuthentications publickey IdentityFile ~/.ssh/id_rsa # github Host github2.com HostName github.com PreferredAuthentications publickey IdentityFile ~/.ssh/feygh # github Host github.com HostName github.com PreferredAuthentications publickey IdentityFile ~/.ssh/id_rsa
6. 关闭密码登录
为了安全性,启用密钥登录之后,最好关闭服务器的密码登录。
对于 OpenSSH 需要编辑 etc/ssh/sshd_config 文件,将 /PasswordAuthentication 这一项设为 no 。
修改配置文件以后,重新启动 sshd 以使新配置生效。